use core::panic;
use std::collections::HashMap;

fn read_varint(bs: &mut impl Iterator<Item = u8>) -> u64 {
    let mut result = 0;
    let mut shift = 0;
    for byte in bs {
        result |= ((byte & 0x7F) as u64) << shift;
        if byte & 0x80 == 0 {
            return result;
        }
        shift += 7;
    }
    panic!("unexpected end of input");
}

thread_local! {
    static WG: WadeGiles = WadeGiles::parse();
}

pub struct WadeGiles {
    table: Vec<String>,
    mapping: HashMap<u32, u16>,
}
impl WadeGiles {
    /// 读取并解析文件 pinyin.bin
    pub fn parse() -> Self {
        /*
           文件格式：
           第一行是一个逗号分隔的字符串，表示拼音表
           之后是一个变长整数，表示映射表的 Unicode 段数
           每段的格式是：
               开头一个变长整数，表示该段相对于上一段末尾的偏移（初始结尾为 0）
               之后是一个变长整数，表示该段的映射表长度
               之后是映射表，每个元素是一个变长整数，表示该字符的拼音在拼音表中的索引
        */
        let data = include_bytes!("pinyin.bin");
        let line_end = data.iter().position(|&c| c == b'\n').unwrap();
        let line = String::from_utf8_lossy(&data[..line_end]);
        let table: Vec<String> = line.split(',').map(|s| s.to_owned()).collect();

        let mut mapping = HashMap::new();
        let mut bs = data[line_end + 1..].iter().copied();
        let num_segments = read_varint(&mut bs);
        let mut i = 0;
        for _ in 0..num_segments {
            i += read_varint(&mut bs) as u32;
            let num_entries = read_varint(&mut bs);
            for _ in 0..num_entries {
                mapping.insert(i, read_varint(&mut bs) as u16);
                i += 1;
            }
        }

        WadeGiles { table, mapping }
    }

    /// 将输入的字符串转换为对应的拼音，忽略无法转换的字符
    pub fn convert<'a, 'b>(&'a self, input: &'b str) -> impl Iterator<Item = &'a str> + 'b
    where
        'a: 'b,
    {
        input.chars().filter_map(|ch| {
            let idx = ch as u32;
            let idx = self.mapping.get(&idx)?;
            self.table.get((*idx) as usize).map(|s| s.as_str())
        })
    }
}

pub fn converter(input: &str) -> String {
    let mut result = String::new();
    WG.with(|wg| {
        for s in wg.convert(input) {
            if !result.is_empty() {
                result.push(' ');
                result += s;
            } else {
                let mut chars = s.chars();
                result.push(chars.next().unwrap().to_ascii_uppercase());
                result.extend(chars);
            }
        }
    });
    result
}
